<script setup lang="ts">
import {
    AmbientLight,
    AnimationAction,
    AnimationMixer,
    Clock,
    Color,
    EquirectangularReflectionMapping,
    GridHelper,
    type Group,
    PerspectiveCamera,
    Scene,
    SkeletonHelper,
    TextureLoader,
    WebGLRenderer,
} from 'three';
import { onMounted, ref, useTemplateRef } from 'vue';
import { ThreeUtil } from 'exploria-ui/src/utils/ThreeUtil.ts';
import type { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

// 网格地面
const controllerSyncRef = useTemplateRef<HTMLElement>('controller-sync');

let fps = ref(30);

const isPlayingAction = ref(false);
const isPlayingVideo = ref(false);
const sliderMaxValue = ref<number>(100); // 动画总帧数

class MyScene {
    static clock = new Clock();
    action: AnimationAction | null = null;
    mixer: AnimationMixer | null = null;
    model: Group | null = null;
    controls: OrbitControls | null = null;
    scene = new Scene();
    renderer: WebGLRenderer | null = null;
    camera: PerspectiveCamera | null = null;
    skeletonHelper: SkeletonHelper | null = null;

    constructor(canvas: HTMLCanvasElement) {
        this.camera = new PerspectiveCamera(
            75,
            canvas.offsetWidth / canvas.offsetHeight,
            0.1,
            1000,
        );
        this.renderer = ThreeUtil.createWebGLRenderer(canvas);
        this.renderer.setSize(canvas.offsetWidth, canvas.offsetHeight);
        this.controls = ThreeUtil.createControls(this.camera, canvas);
        this.camera.position.set(0, 200, 500);
        this.camera.lookAt(0, 20, 0);

        this.addAmbientLight();
    }

    createAnimationMixer() {
        this.mixer = new AnimationMixer(this.model!);
    }

    createAction() {
        this.action = this.mixer!.clipAction(this.model!.animations[0]!);
    }

    playAction() {
        this.action?.reset().fadeIn(0).play();
    }

    removeModelFromScene() {
        if (this.model) {
            this.scene.remove(this.model);
        }
    }

    loadModel(src: string) {
        return new Promise<void>((resolve, reject) => {
            isLoading.value = true;
            modelLoadingProgress.value = 0;
            ThreeUtil.loadFBX(src, (xhr) => {
                modelLoadingProgress.value = parseFloat(
                    ((xhr.loaded / xhr.total) * 100).toFixed(2),
                );
            })
                .then((model: Group) => {
                    modelLoadingProgress.value = 100;
                    // data.scale.set(0.01, 0.01, 0.01);
                    model.castShadow = true;
                    this.model = model;
                    resolve();
                })
                .catch((err) => {
                    reject(err);
                })
                .finally(() => {
                    isLoading.value = false;
                });
        });
    }

    loadBone(src: string) {
        return new Promise<void>((resolve, reject) => {
            isLoading.value = true;
            modelLoadingProgress.value = 0;
            ThreeUtil.loadFBX(src, (xhr: { loaded: number; total: number }) => {
                modelLoadingProgress.value = parseFloat(
                    ((xhr.loaded / xhr.total) * 100).toFixed(2),
                );
            })
                .then((model: Group) => {
                    modelLoadingProgress.value = 100;
                    // data.scale.set(0.01, 0.01, 0.01);
                    this.model = model;
                    resolve();
                })
                .catch((err) => {
                    reject(err);
                })
                .finally(() => {
                    isLoading.value = false;
                });
        });
    }

    showSkeletonHelperToScene() {
        if (this.skeletonHelper) {
            this.scene.remove(this.skeletonHelper);
        }
        // 显示骨骼
        this.skeletonHelper = new SkeletonHelper(this.model!);
        this.scene.add(this.skeletonHelper);
    }

    /**
     * 添加模型到场景
     */
    addModelToScene() {
        this.scene.add(this.model!);
    }

    /**
     * 添加环境贴图
     */
    addEnvMapTexture() {
        // 将渐变纹理设置为场景的环境贴图和背景
        const textureLoader = new TextureLoader();
        const envMapTexture = textureLoader.load(
            new URL('/threejs/model/scene/EnvMapTexture.jpg', import.meta.url)
                .pathname,
        );
        envMapTexture.mapping = EquirectangularReflectionMapping; //正常只是一张图平铺，设置这个可以让图包围环绕整个环境
        this.scene.environment = envMapTexture;
        this.scene.background = envMapTexture;
    }

    /**
     * 设置场景背景色
     * @param color
     */
    setBackgroundColor(color: Color) {
        this.scene.background = color;
    }

    /**
     * 添加环境光
     */
    addAmbientLight() {
        const ambientLight = new AmbientLight(0xffffff, 4);
        this.scene.add(ambientLight);
    }

    addGridHelper() {
        const gridHelper = new GridHelper(10000, 200);
        gridHelper.material.opacity = 0.2;
        gridHelper.material.transparent = true;
        this.scene.add(gridHelper);
    }

    /**
     * 添加地面
     */
    addFloor() {
        ThreeUtil.loadGlb(
            new URL('/threejs/model/scene/羽毛球场.glb', import.meta.url)
                .pathname,
        ).then((model) => {
            const floor = model.scenes[0]!;
            floor.castShadow = true;
            floor.scale.set(70, 70, 70);
            this.scene.add(floor);
        });
    }
}

const isLoading = ref(false);
const modelLoadingProgress = ref<number>(0);

const playMotion = () => {
    isPlayingVideo.value = false;
    isPlayingAction.value = false;

    // 删除之前的模型
    scene1?.removeModelFromScene();
    scene2?.removeModelFromScene();
    // 同时加载模型
    Promise.all([
        scene1?.loadModel(
            new URL('/threejs/model/motion/双脚内侧踢球.fbx', import.meta.url)
                .pathname,
        ),
        scene2?.loadBone(
            new URL('/threejs/model/bone/双脚内侧踢球.fbx', import.meta.url)
                .pathname,
        ),
    ]).then(() => {
        // 创建动画混合器
        scene2?.createAnimationMixer();
        scene1?.createAnimationMixer();

        // 创建骨骼辅助器
        scene2?.showSkeletonHelperToScene();

        scene2?.addModelToScene();
        scene1?.addModelToScene();

        scene2?.createAction();
        scene1?.createAction();

        // 播放动画
        scene1?.playAction();
        scene2?.playAction();

        isPlayingAction.value = true;
    });
};

let scene1: MyScene | null = null;
let scene2: MyScene | null = null;
let controllerScene: MyScene | null = null;
onMounted(() => {
    scene1 = new MyScene(
        controllerSyncRef.value?.querySelector(
            '.canvas-1',
        ) as HTMLCanvasElement,
    );
    scene2 = new MyScene(
        controllerSyncRef.value?.querySelector(
            '.canvas-2',
        ) as HTMLCanvasElement,
    );
    controllerScene = new MyScene(
        controllerSyncRef.value?.querySelector(
            '.controller-canvas',
        ) as HTMLCanvasElement,
    );
    scene1.addFloor();
    scene1.addEnvMapTexture();
    scene2.addGridHelper();
    scene2.setBackgroundColor(new Color(0x333333));

    playMotion();

    // 同步两个场景的控制器（观察者模式）
    controllerScene?.controls?.addEventListener('change', () => {
        scene1?.controls?.object.copy(controllerScene?.controls?.object!);
        scene2?.controls?.object.copy(controllerScene?.controls?.object!);
    });

    // 记录 FPS ——————————————————————————————————————————————— 记录 FPS ————————————
    const setFPS = (_fps: number) => (fps.value = _fps);

    let count = 0;
    let prevTimestamp = 0;

    (function animate(timestamp: number) {
        prevTimestamp = timestamp;
        count++;
        // 间隔超过 1s，将之前计算的 count 输出
        if (timestamp - prevTimestamp >= 1000) {
            setFPS(count);
            count = 0;
        }
        // ——————————————————————————————————————————————————————————— 记录 FPS ——

        const delta = MyScene.clock.getDelta();
        scene1?.mixer?.update(delta);
        scene2?.mixer?.update(delta);

        if (scene1!.action) {
            sliderMaxValue.value =
                scene1!.action.getClip().duration * fps.value;
            // 只有加了这个才能保证同步，注意注意
            scene2!.action!.time = scene1!.action.time;
        }

        scene1?.controls?.update();
        scene2?.controls?.update();
        scene1?.renderer?.render(scene1.scene, scene1.camera!);
        scene2?.renderer?.render(scene2.scene, scene2.camera!);
        requestAnimationFrame((timestamp) => animate(timestamp));
    })(new Date().getTime());
});
</script>

<template>
    <div class="controller-sync" ref="controller-sync">
        <div class="canvas-1-container">
            <canvas class="canvas-1" />
            <canvas class="controller-canvas" />
        </div>
        <canvas class="canvas-2" />
        <div class="mark" v-show="isLoading">
            <p>加载中...</p>
        </div>
    </div>
</template>

<style scoped lang="scss">
.controller-sync {
    position: relative;

    .mark {
        position: absolute;
        inset: 0;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 20px;
        background-color: rgba(255, 255, 255, 0.5);
        backdrop-filter: blur(10px);
    }

    .canvas-1-container {
        width: 300px;
        height: 200px;
        position: relative;

        .controller-canvas {
            position: absolute;
            inset: 0;
        }
    }
    .canvas-2 {
        width: 300px;
        height: 200px;
    }
}
</style>
